08. Include 防范

这段代码有什么问题?

看看这个 main.cpp 文件,然后点击 测试运行 按钮。为什么这段代码不能编译呢?

Start Quiz:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    
    string color;
    int doors;
    
    color = "blue";
    doors = 4;
    
    string color;
    color = "red";
    
    cout << "This " << color << " car has " << doors << " doors";
    
    return 0;
    
}

双重声明

为什么代码没有编译?

SOLUTION: 颜色变量被多次声明。

多次声明变量、函数或类

如果变量、函数或类被声明多次,C++ 程序将不会编译。当代码量小时,这似乎很容易避免。但想象一下,如果你有一个很大的代码库,其中包含许多不同的类和 .cpp 文件,还有很多程序员负责不同代码部分。这时候会发生什么?

看一下这段代码。在两个单独的文件中有两个不同的类。一个类表示一个引擎,它拥有一个存储引擎大小的变量。另一类表示一辆汽车,它有一个颜色变量和一个表示门数的变量。

但有一个问题。为了存储汽车的引擎属性,汽车类同时也会使用引擎类。因此,car.h 头文件中使用以下代码包含引擎头文件:

# include "engine.h"

但是,这段代码在当前状态下不会被编译。考虑一下,为什么按下“测试运行”按钮时它不能正确编译?

提示:看一下 main.cpp 顶部的 include 语句。Include 语句实际上将文件复制了到另一个文件中。请记住,多次定义某个类的程序将不能编译。

Start Quiz:

#include <iostream>
#include <string>
#include "engine.h"
#include "car.h"

using namespace std;

int main() {

	Engine enginelarge("4L");
	Engine enginesmall("2.5L");

	Car carone("red", 5);
	Car cartwo("green", 4);


	cout << "Small engine size "  << enginesmall.getSize() << endl;
	cout << "Large engine size " << enginelarge.getSize() << endl;
	cout << "Car one doors " << carone.getDoors() << endl;
	cout << "Car two doors " << cartwo.getDoors() << endl;

	cout << "Car one engine size " << carone.getEngine() << endl;
	carone.setEngine(enginelarge.getSize());
	cout << "Car one engine size new " << carone.getEngine() << endl;

	return 0;
}
#include <string>

class Engine
{
	private:
		std::string enginesize;

	public:

		Engine ();

		Engine (std::string);

		void setSize(std::string);

		std::string getSize();

};
#include "engine.h"

using namespace std;

Engine::Engine () {
	enginesize = "4L";
}

Engine::Engine (string engine) {
	enginesize = engine;
}

void Engine::setSize(string newsize) {
	enginesize = newsize;
}

string Engine::getSize() {
	return enginesize;
}

#include <string>
#include "engine.h"

class Car
{
	private:
		std::string color;
		int doors;
		Engine enginetype;

	public:
		Car (std::string, int);

		void setColor(std::string);
		void setDoors(int);
		void setEngine(std::string);

		std::string getColor();
		int getDoors();
		std::string getEngine();

};
#include "car.h"

using namespace std;

Car::Car (string newcolor, int newdoors) {
	color = newcolor;
	doors = newdoors;
	enginetype.setSize("2.5L");
}

string Car::getColor() {
	return color;
}

int Car::getDoors() {
	return doors;
}

string Car::getEngine() {
	return enginetype.getSize();
}

void Car::setColor(string newcolor) {
	color = newcolor;
}

void Car::setDoors(int newdoors) {
	doors = newdoors;
}

void Car::setEngine(string newengine) {
	enginetype.setSize(newengine);
}

Compiling

为什么代码没有编译?

SOLUTION: engine 类在 main.cpp 中声明了两次

由于以下语句,代码将不会被编译:

# include "engine.h"
# include "car.h"

第一个 include 语句会将引擎头文件的内容复制到 main.cpp 中。这样 main.cpp 将拥有 Engine 类的定义。

但是,main.cpp 也会复制 “car.h” 的内容。但 “car.h” 文件同样包含 engine.h:

# include "engine.h"

“engine.h”文件最终被包含了两次,所 以 Engine 类被声明了两次。Car 使用了引擎类,main.cpp 也使用了引擎类。

.cpp 文件和 .h 文件的模块化是 C++ 的一大优点。但是,我们如何才能避免多重声明?

ifndef

解决方法是使用 # ifndef 语句,它允许你实现一种名为包含警卫的技术。

ifndef 语句表示“如果未定义”。当用 # ifndef 语句包装头文件时,如果文件尚未定义,编译器将只包含一个头文件。在当前的 main.cpp 例子中,“engine.h” 文件将首先被包含。然后编译器会包含 “car.h”。但 “car.h” 会再次尝试包含 “engine.h”;然而,“engine.h” 文件中的包含警卫将确保 “engine.h” 不会再次包含在内。

下面是使用 ifndef 语句包含 “engine.h” 文件的样子:

# ifndef ENGINE_H
# define ENGINE_H

# include <string>

class Engine
{
    private:
        std::string enginesize;

    public:

        Engine ();

        Engine (std::string);

        void setSize(std::string);

        std::string getSize();

};

# endif /* ENGINE_H */

# ifndef FILENAME_H
# define FILENAME_H

header code ...

# endif /* FILENAME_H */

_H 使用全部大写是命名约定。在 # endif 语句之后加上注释也是惯例。

最好使用 # ifndef 语句来包装所有的头文件。这样,其他程序在使用你的代码时,就不必跟踪已经包含了哪些文件。

以下是在引擎和汽车头文件中包含 # ifndef 语句的结果。点击“测试运行”,即可看到现在编译的代码。

Start Quiz:

#include <iostream>
#include <string>
#include "engine.h"
#include "car.h"

using namespace std;

int main() {

	Engine enginelarge("4L");
	Engine enginesmall("2.5L");

	Car carone("red", 5);
	Car cartwo("green", 4);


	cout << "Small engine size "  << enginesmall.getSize() << endl;
	cout << "Large engine size " << enginelarge.getSize() << endl;
	cout << "Car one doors " << carone.getDoors() << endl;
	cout << "Car two doors " << cartwo.getDoors() << endl;

	cout << "Car one engine size " << carone.getEngine() << endl;
	carone.setEngine(enginelarge.getSize());
	cout << "Car one engine size new " << carone.getEngine() << endl;

	return 0;
}
#ifndef ENGINE_H
#define ENGINE_H

#include <string>

class Engine
{
	private:
		std::string enginesize;

	public:

		Engine ();

		Engine (std::string);

		void setSize(std::string);

		std::string getSize();

};

#endif /* ENGINE_H */
#include "engine.h"

using namespace std;

Engine::Engine () {
	enginesize = "4L";
}

Engine::Engine (string engine) {
	enginesize = engine;
}

void Engine::setSize(string newsize) {
	enginesize = newsize;
}

string Engine::getSize() {
	return enginesize;
}

#ifndef CAR_H
#define CAR_H

#include <string>
#include "engine.h"

class Car
{
	private:
		std::string color;
		int doors;
		Engine enginetype;

	public:
		Car (std::string, int);

		void setColor(std::string);
		void setDoors(int);
		void setEngine(std::string);

		std::string getColor();
		int getDoors();
		std::string getEngine();

};

#endif  /* CAR_H */
#include "car.h"

using namespace std;

Car::Car (string newcolor, int newdoors) {
	color = newcolor;
	doors = newdoors;
	enginetype.setSize("2.5L");
}

string Car::getColor() {
	return color;
}

int Car::getDoors() {
	return doors;
}

string Car::getEngine() {
	return enginetype.getSize();
}

void Car::setColor(string newcolor) {
	color = newcolor;
}

void Car::setDoors(int newdoors) {
	doors = newdoors;
}

void Car::setEngine(string newengine) {
	enginetype.setSize(newengine);
}

头文件中的命名空间

另外,你会注意到,头文件没有使用标准的命名空间。通常建议避免在头文件中使用命名空间。这有助于避免后期函数和类在代码库的不同部分被重用时发生命名冲突。